﻿// (c) Norbert Huffschmid
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using SpinDotters.Core;

namespace SpinDotters.Widgets
{
    /// <summary>
    /// Datepicker control
    /// </summary>
    [TemplatePart(Name = "PART_JogDial", Type = typeof(JogDial))]
    [TemplateVisualState(Name = "Valid", GroupName = "ValidityStates")]
    [TemplateVisualState(Name = "Invalid", GroupName = "ValidityStates")]
    public partial class DatePicker : SpinningControlContainer, INotifyPropertyChanged
    {
        /// <summary>
        /// Notifies listeners that a property value has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Occurs when the SelectedDate property has changed.
        /// </summary>
        public event EventHandler SelectedDateChanged;


        private const int DEFAULT_YEAR_RANGE = 10;
        private const Visibility DEFAULT_YEAR_VISIBILITY = Visibility.Visible;
        private const Visibility DEFAULT_JOGDIAL_VISIBILITY = Visibility.Visible;

        private IList<string> dayNames;
        private IList<string> monthNames;
        private JogDial jogDial;


        /// <summary>
        /// SelectedDate dependency property.
        /// </summary>
        public static readonly DependencyProperty SelectedDateProperty =
            DependencyProperty.Register(
                "SelectedDate",
                typeof(Nullable<DateTime>),
                typeof(DatePicker),
                new PropertyMetadata(DateTime.Now, new PropertyChangedCallback(OnSelectedDateChanged))
            );

        [Description("Gets or sets the currently selected date.")]
        [Category("SpinDotters")]
        public Nullable<DateTime> SelectedDate
        {
            get { return (Nullable<DateTime>)GetValue(SelectedDateProperty); }
            set
            {
                if ((value < this.DisplayDateStart) || (value > this.DisplayDateEnd))
                    throw new ArgumentOutOfRangeException("Date is not with min/max bounds!");

                SetValue(SelectedDateProperty, value);
            }
        }

        /// <summary>
        /// DisplayDateStart dependency property.
        /// </summary>
        public static readonly DependencyProperty DisplayDateStartProperty =
            DependencyProperty.Register(
                "DisplayDateStart",
                typeof(DateTime),
                typeof(DatePicker),
                new PropertyMetadata(DateTime.Now.AddYears(-DEFAULT_YEAR_RANGE),
                    new PropertyChangedCallback(OnStartEndChanged)));

        [Description("Gets or sets the first date to be displayed.")]
        [Category("SpinDotters")]
        public DateTime DisplayDateStart
        {
            get { return (DateTime)GetValue(DisplayDateStartProperty); }
            set
            {
                if (this.DisplayDateEnd < value)
                    this.DisplayDateEnd = value;

                if (this.SelectedDate < value)
                    this.SelectedDate = value;

                SetValue(DisplayDateStartProperty, value);
            }
        }

        /// <summary>
        /// DisplayDateEnd dependency property.
        /// </summary>
        public static readonly DependencyProperty DisplayDateEndProperty =
            DependencyProperty.Register(
                "DisplayDateEnd",
                typeof(DateTime),
                typeof(DatePicker),
                new PropertyMetadata(DateTime.Now.AddYears(DEFAULT_YEAR_RANGE),
                    new PropertyChangedCallback(OnStartEndChanged)));

        [Description("Gets or sets the last date to be displayed.")]
        [Category("SpinDotters")]
        public DateTime DisplayDateEnd
        {
            get { return (DateTime)GetValue(DisplayDateEndProperty); }
            set
            {
                if (this.DisplayDateStart > value)
                    this.DisplayDateStart = value;

                if (this.SelectedDate > value)
                    this.SelectedDate = value;

                SetValue(DisplayDateEndProperty, value);
            }
        }

        /// <summary>
        /// YearVisibility dependency property.
        /// </summary>
        public static readonly DependencyProperty YearVisibilityProperty =
            DependencyProperty.Register(
                "YearVisibility",
                typeof(Visibility),
                typeof(DatePicker),
                new PropertyMetadata(DEFAULT_YEAR_VISIBILITY));

        [Description("Gets or sets the year visibility.")]
        [Category("SpinDotters")]
        public Visibility YearVisibility
        {
            get { return (Visibility)GetValue(YearVisibilityProperty); }
            set { SetValue(YearVisibilityProperty, value); }
        }

        /// <summary>
        /// JogDialVisibility dependency property.
        /// </summary>
        public static readonly DependencyProperty JogDialVisibilityProperty =
            DependencyProperty.Register(
                "JogDialVisibility",
                typeof(Visibility),
                typeof(DatePicker),
                new PropertyMetadata(DEFAULT_JOGDIAL_VISIBILITY));

        [Description("Gets or sets the jogdial visibility.")]
        [Category("SpinDotters")]
        public Visibility JogDialVisibility
        {
            get { return (Visibility)GetValue(JogDialVisibilityProperty); }
            set { SetValue(JogDialVisibilityProperty, value); }
        }


        /// <summary>
        /// Default constructor
        /// </summary>
        public DatePicker()
        {
            this.DefaultStyleKey = typeof(DatePicker);

            // fill day name list according to current culture
            this.dayNames = new List<string>();
            foreach (string dayName in CultureInfo.CurrentCulture.DateTimeFormat.DayNames)
            {
                this.dayNames.Add(dayName);
            }

            // fill month name list according to current culture
            this.monthNames = new List<string>();
            foreach (string monthname in CultureInfo.CurrentCulture.DateTimeFormat.MonthNames)
            {
                // MonthNames return 13 entries!!!
                if (monthname.Length > 0)
                {
                    this.monthNames.Add(monthname);
                }
            }

            this.GotFocus += new RoutedEventHandler(DatePicker_GotFocus);
            this.LostFocus += new RoutedEventHandler(DatePicker_LostFocus);

            this.DataContext = this;

            SetModelValues();
        }


        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.jogDial = this.GetTemplateChild("PART_JogDial") as JogDial;
        }


        protected virtual void OnSelectedDateChanged()
        {
            if (this.SelectedDateChanged != null)
                this.SelectedDateChanged(this, EventArgs.Empty);
        }

        protected virtual void OnPropertyChanged(String info)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(info));
        }


        private void DatePicker_GotFocus(object sender, RoutedEventArgs e)
        {
            UpDownBase upDownCtrl = e.OriginalSource as UpDownBase;

            if ((upDownCtrl != null) && (this.jogDial != null))
            {
                // control selected calendar item with jogdial
                upDownCtrl.BindToDragControl(this.jogDial);
            }
        }

        private void DatePicker_LostFocus(object sender, RoutedEventArgs e)
        {
            UpDownBase upDownCtrl = e.OriginalSource as UpDownBase;

            if ((upDownCtrl != null) && (this.jogDial != null))
            {
                // remove jogdial connection of calendar item
                upDownCtrl.UnbindDragControl();
            }
        }

        private static void OnSelectedDateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            DatePicker dp = (DatePicker)o;

            dp.OnSelectedDateChanged();

            if (dp.SelectedDate != null)
            {
                dp.SetModelValues();

                dp.OnPropertyChanged("DayNameIndex");
                dp.OnPropertyChanged("DayHigh");
                dp.OnPropertyChanged("DayLow");
                dp.OnPropertyChanged("MonthNameIndex");
                dp.OnPropertyChanged("Year");

                VisualStateManager.GoToState(dp, "Valid", true);
            }
            else
            {
                VisualStateManager.GoToState(dp, "Invalid", true);
            }
        }

        private static void OnStartEndChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            DatePicker dp = (DatePicker) o;

            dp.OnPropertyChanged("YearMinimum");
            dp.OnPropertyChanged("YearMaximum");
        }
    }
}
